/*
 * SipServer.cpp
 *
 *  Created on: Dec 10, 2011
 *      Author: shaohong
 */
#include <arpa/inet.h>
#include <arpa/inet.h>
#include <errno.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include "SipServer.h"
#include "NmpsException.h"
#include "AppLogger.h"
#include "RtpStreamer.h"

#define MYPORT "5060"    // the port users will be connecting to
#define MAXBUFLEN 1500

using namespace nmps;
using namespace std;

static AppLogger * logger = AppLogger::getInstance("SipServer");

namespace nmps {

string SipServer::contactAddress = "";

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa) {
	if (sa->sa_family == AF_INET) {
		return &(((struct sockaddr_in*) sa)->sin_addr);
	}

	return &(((struct sockaddr_in6*) sa)->sin6_addr);
}

void* startSipServer(void* arg) {
	// initialization code copied from Beej's Guide to Network Programming
	// http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html#datagram

	SipServerThreadParams * sipserverThreadParams =
			static_cast<SipServerThreadParams*>(arg);

	SipServer * sipServer = sipserverThreadParams->sipServer;

	struct addrinfo hints, *servinfo, *p;
	int rv;
	int numbytes;
	struct sockaddr_storage their_addr;
	char buf[MAXBUFLEN];
	socklen_t addr_len;
	char s[INET6_ADDRSTRLEN];

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_INET; //AF_UNSPEC; // set to AF_INET to force IPv4
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = AI_PASSIVE; // use my IP

	if ((rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		exit(EXIT_FAILURE);
	}

	// loop through all the results and bind to the first we can
	for (p = servinfo; p != NULL; p = p->ai_next) {
		if ((sipServer->serverSocketFD = socket(p->ai_family, p->ai_socktype,
				p->ai_protocol)) == -1) {
			perror("listener: socket");
			continue;
		}

		if (bind(sipServer->serverSocketFD, p->ai_addr, p->ai_addrlen) == -1) {
			close(sipServer->serverSocketFD);
			perror("listener: bind");
			continue;
		}

		break;
	}

	if (p == NULL) {
		fprintf(stderr, "listener: failed to bind socket\n");
		exit(EXIT_FAILURE);
	}

	freeaddrinfo(servinfo);

	while (true) {
		addr_len = sizeof their_addr;
		if ((numbytes = recvfrom(sipServer->serverSocketFD, buf, MAXBUFLEN - 1,
				0, (struct sockaddr *) &their_addr, &addr_len)) == -1) {
			perror("recvfrom");
			exit(EXIT_FAILURE);
		}

		logger->debug(
				"receive message from from %s",
				inet_ntop(their_addr.ss_family,
						get_in_addr((struct sockaddr *) &their_addr), s,
						sizeof s));
		buf[numbytes] = '\0';
		logger->debug("SIP Message is\n%s", buf);

		sipServer->handleSipMessage(buf);

	}

	close(sipServer->serverSocketFD);

	return 0;
}


SipServer::SipServer() {
	serverSocketFD = 0;
}

SipServer::~SipServer() {
}

void SipServer::sendSipMessage(SipMessage * sipMsg) {
	logger->debug("sending message:\n%s", sipMsg->toString().c_str());
	Hop * nextHop = sipMsg->getTargetAddr();

	logger->debug("sending message to %s:%d", nextHop->ip.c_str(), nextHop->port);

	struct sockaddr_in si_other;
	unsigned int slen = sizeof(si_other);

	memset((char *) &si_other, 0, sizeof(si_other));
	si_other.sin_family = AF_INET;
	si_other.sin_port = htons(nextHop->port);
	if (inet_aton(nextHop->ip.c_str(), &si_other.sin_addr) == 0) {
		fprintf(stderr, "inet_aton() failed\n");
		exit(1);
	}

	const char * msgToSend = sipMsg->toString().c_str();
	if (sendto(serverSocketFD, (const void *) msgToSend,
			(unsigned long int) strlen(msgToSend), (int) 0,
			(const sockaddr *) &si_other, slen) == -1) {
		fprintf(stderr, "failed to send out msg\n%s", msgToSend);
		exit(1);
	}

}

string SipServer::getLocalInterfaceIP() {
	struct ifaddrs * ifAddrStruct = NULL;
	struct ifaddrs * ifa = NULL;
	void * tmpAddrPtr = NULL;

	getifaddrs(&ifAddrStruct);

	for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4
			// is a valid IP4 Address
			tmpAddrPtr = &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr;
			char addressBuffer[INET_ADDRSTRLEN];
			inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
			if (0 == strcmp(addressBuffer, string("127.0.0.1").c_str())) {
				continue;
			} else {
				return string(addressBuffer);
				break;
			}
		}
	}

	return "";
}

/**
 * code borrowed from http://stackoverflow.com/questions/212528/linux-c-get-the-ip-address-of-local-computer
 */
string SipServer::getContactAddress() {
	if (!contactAddress.empty()) {
		return contactAddress;
	}

	string localIP = getLocalInterfaceIP();
	contactAddress = string("<sip:");
	contactAddress.append(localIP).append(":5060>");

	return contactAddress;

}

void SipServer::removeDialog(string callid) {

	logger->debug("removing dialong with call-id:%s", callid.c_str());

	map<string, SipDialog *>::iterator itr = sipDialogMap.begin();
	while(itr != sipDialogMap.end()){
		if (0 == itr->first.compare(callid)){
			sipDialogMap.erase(itr);
		}
		itr ++;
	}
}
void SipServer::handleSipMessage(char * receiveMsg) {
	try {
		SipMessage * sipMsg = SipMessage::parseSipMessage(string(receiveMsg));
		SipRequest * sipReq;
		if (sipMsg->isRequest()) {
			sipReq = dynamic_cast<SipRequest *>(sipMsg);
			string callID = sipReq->getCallID();
			SipDialog * sipDialog = this->sipDialogMap[callID];
			if (NULL == sipDialog) {
				logger->debug(
						"didn't find existing call. Create a new Dialog with callid %s",
						callID.c_str());
				sipDialog = new SipDialog(callID);

				//associate the sipDialog with the sip server
				sipDialog->setSipServer(this);
				this->sipDialogMap[callID] = sipDialog;
			}
			//associate the message with the sip dialog
			sipReq->setSipDialog(sipDialog);

			sipDialog->handleRequest(sipReq);
		}
	} catch (NmpsException & e) {
		logger->error(e.description());
	}
}

//add radio streaming, of channelNo, to client with the given RTP IP and Port
void SipServer::addRadioStreaming(unsigned int channelNo, string clientIP,
		int clientPort) {
	logger->debug("adding RadioStreaming of Channel #%d to %s:%d", channelNo,
			clientIP.c_str(), clientPort);

	if (channelNo > radioStreamerList.size()) {
		logger->error("channel number %d is out of range!", channelNo);
	}

	RadioStreamer * radioStreamer = radioStreamerList[channelNo-1];
	radioStreamer->addRecipient(clientIP, clientPort);

	if (!radioStreamer->isStreamingStarted()) {
		radioStreamer->startStreaming();
		while ( ! radioStreamer->isTranscodingStarted()){
			sleep(1);
		}

	}

}

//remove the given client from radio streaming of channelNo
void SipServer::removeRadioStreaming(int channelNo, string clientIP,
		int clientPort) {
	logger->debug("removing RadioStreaming of Channel #%d to %s:%d", channelNo,
			clientIP.c_str(), clientPort);

	RadioStreamer * radioStreamer = radioStreamerList[channelNo - 1];
	radioStreamer->removeReceipient(clientIP, clientPort);

//	if (radioStreamer->getRecipientList()->empty()) {
//		radioStreamer->stopStreaming();
//	}
}

int SipServer::getNumberOfRadioStations() {
	return radioStreamerList.size();
}

void SipServer::addRadioStreamer(RadioStreamer * radioStreamer) {
	radioStreamerList.push_back(radioStreamer);
}

pthread_t SipServer::getSipServerThread() {
	return sipServerThread;
}

bool SipServer::isValidChannelNumber(unsigned int channelNumber) {
	if (channelNumber <=0) {
		return false;
	}

	if (channelNumber > radioStreamerList.size()){
		return false;
	}

	return true;
}

//start the SipServer service
void SipServer::startService() {
	logger->info("Starting SipServer Service");

	// fire off the transcoding thread
	SipServerThreadParams * sipserverThreadParams = new SipServerThreadParams;
	sipserverThreadParams->sipServer = this;
	pthread_create(&sipServerThread, NULL, startSipServer,
			sipserverThreadParams);

}


} // enf of namespace nmps;
